﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Meebey.SmartIrc4net;

using Wormhole.Engine.Communication.Messages;

namespace Wormhole.Engine.Communication
{
	/// <author>Hannes Foulds</author>
	/// <date>01 September 2008</date>
	/// <summary>
	/// This implementation of the communication engine uses IRC.
	/// </summary>
	public class IrcClient : IClient
	{
		#region Events
		/// <summary>
		/// This event is raised when and open game is received.
		/// </summary>
		public event OpenGameReceivedDelegate OpenGameReceived;

		/// <summary>
		/// This event is raised when a the random seed of the other player is received.
		/// </summary>
		public event RandomSeedReceivedDelegate RandomSeedReceived;

		/// <summary>
		/// The deck received event.
		/// </summary>
		public event DeckReceivedDelegate DeckReceived;

		/// <summary>
		/// This event is raised when a response to a game join request is received.
		/// </summary>
		public event JoinResponseDelegate JoinResponse;

		/// <summary>
		/// This event is raised when another player requests a game join.
		/// </summary>
		public event JoinRequestDelegate JoinRequest;

		/// <summary>
		/// This event is raised when a game is closed.
		/// </summary>
		public event GameClosedDelegate GameClosed;

		/// <summary>
		/// This event is taised when the kept hand message is received.
		/// </summary>
		public event KeptHandDelegate KeptHand;

		/// <summary>
		/// This event is raised when the opponent took a mulligan.
		/// </summary>
		public event TookMulliganDelegate TookMulligan;

		/// <summary>
		/// This event is raised when a card action is performed.
		/// </summary>
		public event CardActionDelegate CardAction;

		/// <summary>
		/// This event is raised when a game action is performed.
		/// </summary>
		public event GameActionDelegate GameAction;

		#endregion

		#region Declarations
		/// <summary>
		/// The IRC client wrapper.
		/// </summary>
		protected Meebey.SmartIrc4net.IrcClient ircClient;

		/// <summary>
		/// The thread of the IRC listener.
		/// </summary>
		protected Thread listeningThread;
		#endregion

		#region Properties

		#region Server
		/// <summary>
		/// The name of the IRC server to connect to.
		/// </summary>
		public string Server { get; set; }
		#endregion

		#region Port
		/// <summary>
		/// The port on which the IRC server listens.
		/// </summary>
		public int Port { get; set; }
		#endregion

		#region Channel
		/// <summary>
		/// The name of the lobby channel.
		/// </summary>
		public string Channel { get; set; }
		#endregion

		#region Player
		/// <summary>
		/// The player name.
		/// </summary>
		public string Player { get; set; }
		#endregion

		#region Game
		/// <summary>
		/// The game 
		/// </summary>
		public Game Game { get; set; }
		#endregion

		#region Game List
		/// <summary>
		/// A list of open games.
		/// </summary>
		public Dictionary<Guid, Game> GameList { get; set; }
		#endregion

		#endregion

		#region Constructor
		/// <summary>
		/// Create a new instance of the class.
		/// </summary>
		/// <param name="server">The IRC server name.</param>
		/// <param name="port">The port the IRC server is at.</param>
		/// <param name="channel">The name of the game channel.</param>
		/// <param name="player">The player name.</param>
		public IrcClient(string server, int port, string channel, string player)
		{
			this.Server = server;
			this.Port = port;
			this.Channel = channel;
			this.Player = player;

			// create the game list
			this.GameList = new Dictionary<Guid, Game>();

			// connect to the irc server.
			this.Connect();
		}
		#endregion

		#region Connect
		/// <summary>
		/// Connect to the IRC server.
		/// </summary>
		protected void Connect()
		{
			// create the irc client
			this.ircClient = new Meebey.SmartIrc4net.IrcClient();
			this.ircClient.SendDelay = 0;

			// wire up the event handlers
			this.ircClient.OnQueryMessage += new Meebey.SmartIrc4net.Delegates.MessageEventHandler(ircClient_OnQueryMessage);
			this.ircClient.OnChannelMessage += new Meebey.SmartIrc4net.Delegates.MessageEventHandler(ircClient_OnChannelMessage);
			this.ircClient.OnQuit += new Meebey.SmartIrc4net.Delegates.QuitEventHandler(ircClient_OnQuit);
			this.ircClient.OnModeChange += new Meebey.SmartIrc4net.Delegates.MessageEventHandler(ircClient_OnModeChange);

			// connect to the irc server
			this.ircClient.Connect(this.Server, this.Port);
			if (this.ircClient.Connected)
			{
				this.ircClient.Login(this.Player, this.Player);

				// create the listening thread
				this.listeningThread = new Thread(new ThreadStart(this.ircClient.Listen));
				//this.listeningThread.Priority = ThreadPriority.BelowNormal;
				this.listeningThread.Start();
				this.ircClient.Join(this.Channel);
			}
			else
			{
				throw new ApplicationException("Could not connect to the IRC server.");
			}
		}
		#endregion

		#region Disconnect
		/// <summary>
		/// Disconnect from the server.
		/// </summary>
		public void Disconnect()
		{
			if (this.ircClient != null)
			{
				if (this.ircClient.Connected)
				{
					this.ircClient.Disconnect();
					this.listeningThread.Abort();
					this.listeningThread.Join();
				}
			}
		}
		#endregion

		#region Event Handlers

		#region On Mode Change
		/// <summary>
		/// Join the channel when the user is logged in.
		/// </summary>
		/// <param name="ircdata"></param>
		private void ircClient_OnModeChange(Data ircdata)
		{
			this.ircClient.Join(this.Channel);
		}
		#endregion

		#region On Query Message
		/// <summary>
		/// Handle a message that was received on private (game) chat.
		/// </summary>
		/// <param name="ircdata">The IRC data that was received.</param>
		void ircClient_OnQueryMessage(Data ircdata)
		{
			Message message = Message.Parse(ircdata.Channel, ircdata.Nick, ircdata.Message, this.GameList);

			if (message != null)
				this.HandleOpponentMessage(message);
		}
		#endregion

		#region On Channel Message
		/// <summary>
		/// Handle a message that was received on the lobby channel.
		/// </summary>
		/// <param name="ircdata">The IRC data that was received.</param>
		private void ircClient_OnChannelMessage(Data ircdata)
		{
			Message message = Message.Parse(ircdata.Channel, ircdata.Nick, ircdata.Message, this.GameList);

			if (message != null)
				this.HandleChannelMessage(message);
		}
		#endregion

		#region On Quit
		/// <summary>
		/// Handle the event when the user quits the IRC.
		/// </summary>
		/// <param name="who"></param>
		/// <param name="quitmessage"></param>
		/// <param name="ircdata"></param>
		private void ircClient_OnQuit(string who, string quitmessage, Data ircdata)
		{
			this.CloseGameForPlayer(who);
		}
		#endregion
		
		#endregion

		#region Handle Channel Message
		/// <summary>
		/// Handle a message that was received on a channel.
		/// </summary>
		/// <param name="message">The message that was received.</param>
		private void HandleChannelMessage(Message message)
		{
			switch (message.Action)
			{
				case Actions.GameListRequest:
					this.HandleGameListRequest((GameListMessage)message);
					break;
				case Actions.GameList:
					this.HandleGameList((GameListMessage)message);
					break;
				case Actions.GameClosed:
					this.HandleGameClosed((GameClosedMessage)message);
					break;
			}
		}
		#endregion

		#region Handle Opponent Message
		/// <summary>
		/// Handle a message received from the opponent.
		/// </summary>
		/// <param name="message">The message that was received.</param>
		private void HandleOpponentMessage(Message message)
		{
			switch (message.Action)
			{
				case Actions.RandomSeed:
					this.HandleRandomSeedRequest((RandomSeedMessage)message);
					break;
				case Actions.GameList:
					this.HandleGameList((GameListMessage)message);
					break;
				case Actions.NewDeck:
					this.HandleDeck((CardMessage)message);
					break;
				case Actions.ContinueDeck:
					this.HandleDeck((CardMessage)message);
					break;
				case Actions.EndDeck:
					this.HandleDeck((CardMessage)message);
					break;
				case Actions.RequestJoin:
					this.HandleJoinRequest((JoinGameMessage)message);
					break;
				case Actions.AcceptJoin:
					this.HandleJoinResponse((JoinGameMessage)message);
					break;
				case Actions.RejectJoin:
					this.HandleJoinResponse((JoinGameMessage)message);
					break;
				case Actions.KeepHand:
					this.HandleHandMessage((HandMessage)message);
					break;
				case Actions.TakeMulligan:
					this.HandleHandMessage((HandMessage)message);
					break;
				case Actions.CardAction:
					this.HandleCardAction((CardActionMessage)message);
					break;
				case Actions.GameAction:
					this.HandleGameAction((GameActionMessage)message);
					break;
			}
		}
		#endregion

		#region Handle Random Seed Request
		/// <summary>
		/// Handle a random seed that was received from the opponent.
		/// </summary>
		/// <param name="message">The message that was received.</param>
		private void HandleRandomSeedRequest(RandomSeedMessage message)
		{
			if (message.PlayerName == this.Game.OpponentName)
			{
				this.OnRandomSeedReceived(message.Seed);
			}
		}
		#endregion

		#region Handle Game List
		/// <summary>
		/// Handle a game list message that was received.
		/// </summary>
		/// <param name="message">The message.</param>
		private void HandleGameList(GameListMessage message)
		{
			this.OnOpenGameReceived(message.Game);
		}
		#endregion

		#region Handle Game List Request
		/// <summary>
		/// Handle a request for an open game list.
		/// </summary>
		/// <param name="message">The request message.</param>
		private void HandleGameListRequest(GameListMessage message)
		{
			this.SendGameListing(message.PlayerName);
		}
		#endregion

		#region Handle Deck
		/// <summary>
		/// Handle a deck that was received over the commuincations channel.
		/// </summary>
		/// <param name="cardMessage"></param>
		private void HandleDeck(CardMessage cardMessage)
		{
			// create a new deck if needed
			if (cardMessage.Action == Actions.NewDeck)
				this.Game.OpponentDeck = new Deck();

			// load the cards that was received
			foreach (Card card in cardMessage.Deck)
			{
				this.Game.OpponentDeck.Add(card);
			}

			// raise the event if a complete deck was received
			if (cardMessage.Action == Actions.EndDeck)
			{
				this.OnDeckReceived(this.Game.OpponentDeck);
			}
		}
		#endregion

		#region Handle Game Closed
		/// <summary>
		/// Handle the game closed message that was received.
		/// </summary>
		/// <param name="message">The message.</param>
		private void HandleGameClosed(GameClosedMessage message)
		{
			this.OnGameClosed(message.Game);
		}
		#endregion

		#region Send Game Listing
		/// <summary>
		/// Send an open game listing.
		/// </summary>
		/// <param name="to">Where the listing should be send.</param>
		private void SendGameListing(string to)
		{
			if ((this.Game != null) && (!this.Game.Started))
			{
				// create the response message
				GameListMessage response = new GameListMessage();
				response.Action = Actions.GameList;
				response.To = to;
				response.Game = this.Game;

				// send the message
				this.SendReponse(response);
			}
		}
		#endregion

		#region Accept
		/// <summary>
		/// Accept a join request.
		/// </summary>
		/// <param name="id">The ID of the game.</param>
		/// <param name="playerName">The name of the player requesting the join.</param>
		/// <param name="response">The message</param>
		public void AcceptJoin(Guid id, string playerName, string message)
		{
			if ((this.Game != null) && (!this.Game.Started) && (this.Game.ID == id))
			{
				// create the message
				JoinGameMessage response = new JoinGameMessage(this.Game, Actions.AcceptJoin, message);
				response.To = playerName;

				// send the message
				this.SendReponse(response);

				// close the game
				this.CloseGame(false);
				this.Game.OpponentName = playerName;
				this.Game.Started = true;
			}
		}
		#endregion

		#region Reject Join
		/// <summary>
		/// Reject a game join request.
		/// </summary>
		/// <param name="id">The ID of the game.</param>
		/// <param name="playerName">The name of the player requesting the join.</param>
		/// <param name="response">The message string.</param>
		public void RejectJoin(Guid id, string playerName, string message)
		{
			// get the game
			Game game = null;
			if (!this.GameList.ContainsKey(id))
			{
				game = new Game();
				game.ID = id;
			}
			else
			{
				game = this.GameList[id];
			}

			// create the message
			JoinGameMessage response = new JoinGameMessage(game, Actions.RejectJoin, message);
			response.To = playerName;

			// send the message
			this.SendReponse(response);
		}
		#endregion

		#region Handle Join Request
		/// <summary>
		/// Handle a request to join the game.
		/// </summary>
		/// <param name="message">The request message.</param>
		private void HandleJoinRequest(JoinGameMessage message)
		{
			if ((this.Game != null) && (!this.Game.Started))
			{
				if (this.Game.ID == message.Game.ID)
				{
					this.OnJoinRequest(this.Game, message.PlayerName);
				}
				else
				{
					this.RejectJoin(message.Game.ID, message.PlayerName, "The game is no longer available");
				}
			}
		}
		#endregion

		#region Handle Join Response
		/// <summary>
		/// Handle the join response message.
		/// </summary>
		/// <param name="message">The request message.</param>
		private void HandleJoinResponse(JoinGameMessage message)
		{
			bool accepted = (message.Action == Actions.AcceptJoin);
			this.OnJoinResponse(message.Game, accepted, message.Response);
		}
		#endregion

		#region Send Response
		/// <summary>
		/// Send a response message.
		/// </summary>
		/// <param name="message">The message to send.</param>
		protected void SendReponse(Message message)
		{
			this.ircClient.Message(SendType.Message, message.To, message.ToString());
		}
		#endregion

		#region Create Game
		/// <summary>
		/// Create a new game
		/// </summary>
		/// <param name="description">The description for the game.</param>
		public void CreateGame(string description)
		{
			// create the game object
			this.Game = new Game();
			this.Game.ID = System.Guid.NewGuid();
			this.Game.PlayerName = this.Player;
			this.Game.Description = description;
			this.Game.Started = false;

			// send a notification of the new game
			this.SendGameListing(this.Channel);
		}
		#endregion

		#region Request Open Game List
		/// <summary>
		/// Request a list of open games.
		/// </summary>
		public void RequestOpenGameList()
		{
			// create the message
			GameListMessage message = new GameListMessage();
			message.Action = Actions.GameListRequest;
			message.To = this.Channel;
			message.Game = this.Game;

			// send the message
			this.SendReponse(message);
		}
		#endregion

		#region On Open Game Received
		/// <summary>
		/// Raise the open game received event.
		/// </summary>
		/// <param name="game">The game that was received.</param>
		protected void OnOpenGameReceived(Game game)
		{
			// add the game to the list of open games
			this.GameList.Add(game.ID, game);

			// raise the event
			if (this.OpenGameReceived != null)
				this.OpenGameReceived(game);
		}
		#endregion

		#region Join Game
		/// <summary>
		/// Send a game join request.
		/// </summary>
		/// <param name="id">The ID of the game to join.</param>
		public void JoinGame(Guid id)
		{
			// get the game to join
			Game game = this.GameList[id];

			// create the message
			JoinGameMessage message = new JoinGameMessage(game, Actions.RequestJoin);
			message.To = game.OpponentName;

			// send the message
			this.SendReponse(message);
		}
		#endregion

		#region Send Random Seed
		/// <summary>
		/// Send a random seed to the other player.
		/// </summary>
		/// <param name="seed">The random seed to send.</param>
		public void SendRandomSeed(int seed)
		{
			// create the message
			RandomSeedMessage message = new RandomSeedMessage();
			message.To = this.Game.OpponentName;
			message.Game = this.Game;
			message.Seed = seed;

			// send the message
			this.SendReponse(message);
		}
		#endregion

		#region On Random Seed Received
		/// <summary>
		/// Rais the random seed received event.
		/// </summary>
		/// <param name="seed">The seed that was received.</param>
		protected void OnRandomSeedReceived(int seed)
		{
			if (this.RandomSeedReceived != null)
				this.RandomSeedReceived(seed);
		}
		#endregion

		#region Send Deck
		/// <summary>
		/// Send a deck to the opponent.
		/// </summary>
		/// <param name="deck">The deck of cards to send.</param>
		public void SendDeck(Deck deck)
		{
			// create the messages
			List<CardMessage> messages = CardMessage.CreateMessages(deck);

			// send the messages
			foreach (CardMessage message in messages)
			{
				message.To = this.Game.OpponentName;
				this.SendReponse(message);
			}
		}
		#endregion

		#region On Deck Received
		/// <summary>
		/// Raise the event when a deck is received.
		/// </summary>
		/// <param name="deck">The deck that was received,</param>
		protected void OnDeckReceived(Deck deck)
		{
			if (this.DeckReceived != null)
			{
				this.DeckReceived(deck);
			}
		}
		#endregion

		#region On Join Response
		/// <summary>
		/// Fire the join response message.
		/// </summary>
		/// <param name="game">The game the message is for.</param>
		/// <param name="accepted">True when the request is accepted.</param>
		/// <param name="message">The response message.</param>
		protected void OnJoinResponse(Game game, bool accepted, string message)
		{
			if (accepted)
			{
				this.Game = game;
				this.Game.PlayerName = this.ircClient.Nickname;
			}

			// raise the event
			if (this.JoinResponse != null)
				this.JoinResponse(game, accepted, message);
		}
		#endregion

		#region On Join Request
		/// Raise the Join Request event.
		/// </summary>
		/// <param name="game">The game the request is for.</param>
		/// <param name="playerName">The name of the player making the request.</param>
		protected void OnJoinRequest(Game game, string playerName)
		{
			if (this.JoinRequest != null)
				this.JoinRequest(game, playerName);
		}
		#endregion

		#region On Game Closed
		/// <summary>
		/// Raise the game closed event.
		/// </summary>
		/// <param name="game">The game that was closed.</param>
		protected void OnGameClosed(Game game)
		{
			// send the message
			if (this.GameClosed != null)
				this.GameClosed(game);

			// remove the game from the collection
			if (this.GameList.ContainsKey(game.ID))
				this.GameList.Remove(game.ID);
		}
		#endregion

		#region Close Game
		/// <summary>
		/// Close the game.
		/// </summary>
		/// <param name="remove">Should the game also be removed.</param>
		public void CloseGame(bool remove)
		{
			if ( (this.Game != null) && (!this.Game.Started) )
			{
				// create the close message
				GameClosedMessage message = new GameClosedMessage(this.Game);
				message.To = this.Channel;

				// send the message
				this.SendReponse(message);

				// clear the current game
				if (remove)
					this.Game = null;
			}
		}
		#endregion

		#region Find Game By Player Name
		/// <summary>
		/// Find a game by a player name.
		/// </summary>
		/// <param name="playerName">The name of the player.</param>
		/// <returns>Returns the game or null if no game was found.</returns>
		protected Game FindGameByPlayerName(string playerName)
		{
			foreach (Game game in this.GameList.Values)
			{
				if (game.OpponentName == playerName)
					return game;
			}

			return null;
		}
		#endregion

		#region Close Game For Player
		/// <summary>
		/// Close a game for a specific player.
		/// </summary>
		/// <param name="playerName"></param>
		private void CloseGameForPlayer(string playerName)
		{
			// find the game to close
			Game game = this.FindGameByPlayerName(playerName);

			// close the game
			if (game != null)
				this.OnGameClosed(game);
		}
		#endregion

		#region Keep Hand
		/// <summary>
		/// Keep the hand that was dealt.
		/// </summary>
		public void KeepHand()
		{
			// create the message
			HandMessage message = new HandMessage(Actions.KeepHand);
			message.To = this.Game.OpponentName;
			message.Game = this.Game;

			// send the message
			this.SendReponse(message);
		}
		#endregion

		#region On Kept Hand
		/// <summary>
		/// Raise the kept hand event.
		/// </summary>
		protected void OnKeptHand()
		{
			if (this.KeptHand != null)
				this.KeptHand();
		}
		#endregion

		#region Take Mulligan
		/// <summary>
		/// Take a mullican.
		/// </summary>
		public void TakeMulligan()
		{
			// create the message
			HandMessage message = new HandMessage(Actions.TakeMulligan);
			message.To = this.Game.OpponentName;
			message.Game = this.Game;

			// send the message
			this.SendReponse(message);
		}
		#endregion

		#region On Took Mulligan
		/// <summary>
		/// Raise the took mulligan event.
		/// </summary>
		protected void OnTookMulligan()
		{
			if (this.TookMulligan != null)
				this.TookMulligan();
		}
		#endregion

		#region Handle Hand Message
		/// <summary>
		/// Handle a hand message.
		/// </summary>
		/// <param name="message">The message that was received.</param>
		private void HandleHandMessage(HandMessage message)
		{
			if (message.Action == Actions.KeepHand)
				this.OnKeptHand();
			else if (message.Action == Actions.TakeMulligan)
				this.OnTookMulligan();
		}
		#endregion

		#region Handle Card Action
		/// <summary>
		/// Handle a card action message.
		/// </summary>
		/// <param name="message">The card action message</param>
		private void HandleCardAction(CardActionMessage message)
		{
			this.OnCardAction(message.CardAction);
		}
		#endregion

		#region Perform Card Action
		/// <summary>
		/// Send a card action message.
		/// </summary>
		/// <param name="action">The card action to send.</param>
		public void PerformCardAction(Action.CardAction action)
		{
			// create the message
			CardActionMessage message = new CardActionMessage(action);
			message.To = this.Game.OpponentName;
			message.Game = this.Game;

			// send the message
			this.SendReponse(message);
		}
		#endregion

		#region On Card Action
		/// <summary>
		/// Raise the card action event.
		/// </summary>
		/// <param name="action">The action the event is for.</param>
		protected void OnCardAction(Action.CardAction action)
		{
			if (this.CardAction != null)
				this.CardAction(action);
		}
		#endregion

		#region Handle Game Action
		/// <summary>
		/// Handle a game action message.
		/// </summary>
		/// <param name="message">The game action message.</param>
		private void HandleGameAction(GameActionMessage message)
		{
			this.OnGameAction(message.GameAction);
		}
		#endregion

		#region On Game Action
		/// <summary>
		/// Raise the game action event.
		/// </summary>
		/// <param name="action">The action the event is for.</param>
		private void OnGameAction(Wormhole.Engine.Communication.Action.GameAction action)
		{
			if (this.GameAction != null)
				this.GameAction(action);
		}
		#endregion

		#region Perform Game Action
		/// <summary>
		/// Send a game action message.
		/// </summary>
		/// <param name="action">The game action to send.</param>
		public void PerformGameAction(Action.GameAction action)
		{
			// create the message
			GameActionMessage message = new GameActionMessage(action);
			message.To = this.Game.OpponentName;
			message.Game = this.Game;

			// send the message
			this.SendReponse(message);
		}
		#endregion
	}
}
